#include "stdafx.h"
#include "BSplineSurface.h"
#include <stdlib.h>
#include <GL\glew.h>
#include <memory.h>
#include <iostream>
using namespace std;

BSplineSurface::BSplineSurface(int m,int n,int pointsM,int pointsN)
{
	this->m = m;
	this->n = n;
	this->values = new Point3D*[pointsM];
	for(int i=0;i<pointsM;++i)
	{
		this->values[i] = new Point3D[pointsN];
		memset(this->values[i],0,pointsN*sizeof(Point3D));
	}
	this->v_knots = this->u_knots = nullptr;
	this->v_knots_n = this->m+pointsM;
	this->u_knots_n = this->n+pointsN;
	this->fillKnots(v_knots,this->v_knots_n);
	this->fillKnots(u_knots,this->u_knots_n);
	this->fillSurfaceValues();
}

BSplineSurface::~BSplineSurface(void)
{
	glDeleteBuffers(1,&this->buffID);
	delete [] this->u_knots;
	delete [] this->v_knots;
	for(int i=0;i<this->m;++i)
	{
		delete [] this->values[i];
	}
	delete [] this->values;
}

void BSplineSurface::fillKnots(float *& knots,int n)
{
	if(!knots)
	{
		knots = new float[n];
	}
	for(int i=0;i<n;++i)
	{
		knots[i] = 0.0f;
		if(i!=0)
		{
			knots[i] = knots[i-1]+1.0f/(float)(n-1);
		}
	}
}

void BSplineSurface::addPoint(int m,int n,Point3D p)
{
	this->values[m][n] = p;
}

double BSplineSurface::divide(double a,double b)
{
	if(a==0.0 && b==0.0)
		return 0.0;
	if(b==0.0)
		return a;
	return a/b;
}

double BSplineSurface::getSplineValue(float t,int i,int n,const float * knots)
{
	if(n==0)
	{
		if(knots[i-1]<=t && t<knots[i])
			return 1;
		else return 0;
	}
	return divide((t-knots[i-1]),(knots[i+n-1]-knots[i-1]))*getSplineValue(t,i,n-1,knots)+divide((knots[i+n]-t),(knots[i+n]-knots[i]))*getSplineValue(t,i+1,n-1,knots);
}

void BSplineSurface::fillSurfaceValues()
{
	GLfloat uStep = 0.005f;
	GLfloat vStep = 0.005f;
	this->buffSize = (int)(1.0f/uStep)*(int)(1.0f/vStep)*4;
	GLdouble * buffData = new GLdouble[this->buffSize];
	int index=0;
	for(float ku = u_knots[0];ku<=u_knots[this->u_knots_n-1-this->m];ku+=uStep)
	{
		for(float kv = v_knots[0];kv<=v_knots[this->v_knots_n-1-this->n];kv+=vStep)
		{
			double valX = 0.0;
			double valY = 0.0;
			double valZ = 0.0;
			for(int i=0;i<=this->m;++i)
			{
				for(int j=0;j<=this->n;++j)
				{
					double bSplineValue_v = this->getSplineValue(kv,j,this->n,v_knots);
					double bSplineValue_u = this->getSplineValue(ku,i,this->m,u_knots);
					valX += this->values[i][j].x*bSplineValue_v*bSplineValue_u;
					valY += this->values[i][j].y*bSplineValue_v*bSplineValue_u;
					valZ += this->values[i][j].z*bSplineValue_v*bSplineValue_u;
				}
			}
			buffData[index++]=valX;
			buffData[index++]=valY;
			buffData[index++]=valZ;
		}
	}
	glGenBuffers(1,&this->buffID);
	glBindBuffer(GL_ARRAY_BUFFER,this->buffID);
	glBufferData(GL_ARRAY_BUFFER,this->buffSize*sizeof(GLdouble),buffData,GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER,0);
	delete [] buffData;
}

void BSplineSurface::draw()
{
	glColor3f(0.0,1.0,0.0);
	glBindBuffer(1,this->buffID);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3,GL_DOUBLE,0,0);
	glDrawElements( GL_POINTS,this->buffSize,GL_DOUBLE, NULL);
	glDisableClientState(GL_VERTEX_ARRAY);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
}

Point3D BSplineSurface::getPointAt(float u,float v)
{
	Point3D result;
	for(int i=0;i<=this->m;++i)
	{
		for(int j=0;j<=this->n;++j)
		{
			double bSplineValue_v = this->getSplineValue(v,j,this->n,v_knots);
			double bSplineValue_u = this->getSplineValue(u,i,this->m,u_knots);
			result.x += (MY_DATATYPE) (this->values[i][j].x*bSplineValue_u*bSplineValue_v);
			result.y += (MY_DATATYPE) (this->values[i][j].y*bSplineValue_u*bSplineValue_v);
			result.z += (MY_DATATYPE) (this->values[i][j].z*bSplineValue_u*bSplineValue_v);
		}
	}
	return result;
}

float BSplineSurface::getVMaxGenerationValue()
{
	return this->v_knots[this->v_knots_n-1-this->n];
}

float BSplineSurface::getUMaxGenerationValue()
{
	return this->u_knots[this->u_knots_n-1-this->m];
}